<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Swiper demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
</head>
<style>
	*{
		padding: 0;
		margin: 0;
		user-select: none;
	}
	:root {
		--h: 400px;
		--w: 600px;
	}
	body {
		height: 100vh;
		width: 100vw;
		display: flex;
		align-items: center;
		justify-content: center;
		flex-direction: column;
	}
	div{
		box-sizing: border-box;
	}
	.swiper-container {
		position: relative;
		height: 400px;
		width: 600px;
		box-shadow: 1px 0 10px 10px #000;
		overflow: hidden;
	}
	.swiper-wraper {
		width: auto;
		white-space: nowrap;	
	}
	.swiper-item {
		height: var(--h);
		width: 600px;
	}
	.jt_t, .jt_l {
		height: 50px;
		width: 20px;
		position: absolute;
		top: 50%;
		transform: translateY(-50%);
		text-align: center;
		line-height: 50px;
		font-size: 20px;
		font-weight: 600;
		color: rgb(255, 255, 255, .3);
	}
	.jt_t {
		height: 20px;
		width: 50px;
		left: 50%;
		transform: translateX(-50%)!important;
		line-height: 20px;
	}
	.right {
		right: 0;
		border-radius: 50% 0 0 50%;
	}
	.left {
		left: 0;
		border-radius: 0 50% 50% 0;
	}
	.top {
		top: 0;
		border-radius: 0 0 50% 50%;
	}
	.bottom {
		top: auto!important;
		bottom: 0;
		border-radius: 50% 50% 0 0;
	}
	.circle {
		position: absolute;
		bottom: 10px;
		left: 50%;
		transform: translateX(-50%);
		display: flex;
	}
	.enter {
		background-color: rgba(0, 0, 0, .25);
		color: #fff;
		cursor: pointer;
	}
	.circle-item {
		display: block;
		height: 20px;
		width: 20px;
		margin: 0 10px;
		flex-shrink: 0;
		border-radius: 50%;
		background-color: #fff;
		opacity: .5;
	}
	.circle-item_t {
		display: block;
		margin: 10px 0;
		height: 20px;
		width: 20px;
		border-radius: 0;
		background-color: #fff;
		opacity: .5;
	}
	.circle_t {
		display: table-cell;
		position: absolute;
		right: 10px;
		top: 50%;
		transform: translateY(-50%);
	}
	.v {
		display: inline-block!important;
		margin-right: -5px!important;
	}
	.show {
		opacity: 1;
	}
	.selected {
		background: linear-gradient(#fff,transparent,#fff);
	}
	.test {
		height: 150px;
		width: 150px;
		background-color: #008000;
		position: absolute;
	}
	.change-type {
		display: block;
		margin: 20px auto;
		padding: 20px 40px;
		background-color: #008000;
		border: none;
		border-radius: 4px;
		outline: none;
		color: #fff;
	}
	.btn_hide {
		display: none;
	}
	.btn_show {
		display: block;
	}
</style>

<body>
	<!-- 轮播图容器 -->
	<div class="swiper-container">
	</div>
	<!-- 改变轮播图模式的按钮 -->
	<button class="change-type">横向轮播</button>
	<script>
		//原生实现单模式轮播， 功能有缺陷
		// let index = 0
		// let timer = null
		// let debounce = true
		// let translateDistance = 0
		// let type = 'Transverse'
		// 每次使用该方法都会操作dom，非常消耗性能
		function $(el, alone = true) {
			if(alone) {
				return document.querySelector(el)
			}
			return document.querySelectorAll(el)
		}
		// for(let i = 0; i < $('.swiper-item', false).length; ++i) {
		// 	const el = document.createElement('i')
		// 	if(i === 0) {
		// 		el.classList.add('selected')
		// 	}
		// 	el.classList.add('circle-item')
		// 	el.setAttribute('data-index', i)
		// 	el.addEventListener('click', () => {
		// 		if(debounce) {
		// 			debounce = false
		// 			let initVlaue = index
		// 			index = el.getAttribute('data-index') - 0
		// 			if(initVlaue - index >0) {
		// 				animate($('.swiper-wraper'), $('.swiper-item').offsetHeight, false, () => {
		// 					debounce = true
		// 					translateDistance = index * $('.swiper-item', false)[index].offsetHeight
		// 				})
		// 			}else if(initVlaue - index <0){
		// 				animate($('.swiper-wraper'), $('.swiper-item').offsetHeight, true, () => {
		// 					debounce = true
		// 					translateDistance = index * $('.swiper-item', false)[index].offsetHeight
		// 				})
		// 			}else {
		// 				return
		// 			}
		// 			removeCircleStyle(index)
		// 		}
		// 	})
		// 	$('.circle').appendChild(el)
		// }
		// const node = $('.swiper-wraper').children[0].cloneNode(true)
		// $('.swiper-wraper').appendChild(node)
		// $('.swiper-container').addEventListener('mouseenter', function() {
		// 	[...$('.jt', false)].forEach(el => {
		// 		el.classList.add('enter')
		// 	});
		// 	[...$('.circle-item', false)].forEach(el => {
		// 		el.classList.add('show')
		// 	});
		// 	clearInterval(timer)
		// })
		// $('.swiper-container').addEventListener('mouseleave', function() {
		// 	[...$('.jt', false)].forEach(el => {
		// 		el.classList.remove('enter')
		// 	});
		// 	[...$('.circle-item', false)].forEach(el => {
		// 		el.classList.remove('show')
		// 	});
		// 	timer = setInterval(() => {
		// 		$('.right').click()
		// 	}, 3000)
		// })
		// $('.right').addEventListener('click', () => {
		// 	if(!debounce) return
		// 	debounce = false
		// 	translateDistance = index * $('.swiper-item', false)[index].offsetHeight
		// 	if(index === 0) {
		// 		$('.swiper-wraper').style.transform = 'translate3d(0, 0, 0)'
		// 	}
		// 	index ++
		// 	animate($('.swiper-wraper'), $('.swiper-item').offsetHeight, true, () => {
		// 		debounce = true
		// 		if(index >= $('.swiper-item', false).length - 1) {
		// 			index = 0
		// 		}
		// 	})
		// 	removeCircleStyle(index===$('.swiper-item', false).length - 1? 0 : index)
		// })
		// $('.left').addEventListener('click', () => {
		// 	if(!debounce) return
		// 	debounce = false
		// 	index --
		// 	if(index < 0) {
		// 		index = $('.swiper-item', false).length - 2
		// 		$('.swiper-wraper').style.transform = 'translate3d(0, -'+(index * $('.swiper-item').offsetHeight)+'px, 0)'
		// 	}
		// 	translateDistance = (index+1) * $('.swiper-item', false)[index].offsetHeight
		// 	removeCircleStyle(index)
		// 	animate($('.swiper-wraper'), $('.swiper-item').offsetHeight, false, () => {
		// 		debounce = true
		// 	})
		// })
		
		// function animate(obj,target,f = true,callback){
		// 	let h
		// 	let step = 0 + (translateDistance -0);
		// 	clearInterval(obj.timer)
		// 	obj.timer=setInterval(function(){
		// 		h = $('.swiper-wraper').style.transform?.split('(')[1]?.match(/\d+/g)[1] - 0|| 0
		// 		step += (target * index - h) /10 
		// 		step=f?Math.ceil(step):Math.floor(step);
		// 		obj.style.transform = 'translate3d(0, -'+step+'px, 0)'
		// 		if(Math.abs(step) === h){
		// 			clearInterval(obj.timer)
		// 			callback&&callback();
		// 		}
		// 	},16)
		// }
		// function removeCircleStyle(index) {
		// 	[...$('.circle-item', false)].forEach(item => {
		// 		item.classList.remove('selected')
		// 	});
		// 	$('.circle-item', false)[index].classList.add('selected')
		// }
		// timer = setInterval(() => {
		// 	$('.right').click()
		// }, 3000)
		</script>
		
	<script>
		//全局定时器变量
		let timer
		//轮播方向变量 纵向（）Transverse 横向（Longitudinal）
		let type = 'Transverse'
		class Animate {
			constructor(type = 'Transverse') {
				//没次创建新的实例清空以前实列的定时器
				clearInterval(timer)
				//初始化节点
				this.initNode()
				//判断当前的轮播方向是纵向还是横向， 纵向计算node 的高度， 横向计算node的宽度
				this.type = type === 'Transverse'? 'offsetHeight' : 'offsetWidth'
				//初始化播放轮播节点索引
				this.index = 0
				//防抖变量
				this.debounce = true
				//当前轮播图移动的距离 用于计算后续移动距离
				this.translateDistance = 0
				//获取轮播图容器节点
				this.sc = document.querySelector('.swiper-container')
				//获取上下左右按钮节点
				this.rbtn = document.querySelector(type === 'Transverse'? '.bottom' : '.right')
				this.lbtn = document.querySelector(type === 'Transverse'? '.top' : '.left')
				//获取轮播图包裹内容的节点
				this.sw = document.querySelector('.swiper-wraper')
				//克隆第一个节点添加到最后
				const node = this.sw.children[0].cloneNode(true)
				this.sw.appendChild(node)
				//获取所有轮播图节点
				this.swiperItem = document.querySelectorAll('.swiper-item');
				//根据当前轮播模式改变轮播图样式
				[...this.swiperItem].forEach((el, i) => {
					if(!i) {
						// 有时会出现获取的宽度与实际宽度不一致的情况,所以将第一个轮播节点的宽度或高度保存为一个变量，以后都是用此变量进行计算
						this.distance = el[this.type]
					}
					el.classList[type === 'Transverse'? 'remove' : 'add']('v')
				});
				// 根据不同模式设置不同类名以改变不同模式下的样式
				document.getElementById('circle-box').className = type === 'Transverse'? 'circle_t' : 'circle',
				// 根据不同模式设置不同类名以改变不同模式下的样式
				this.jt = document.querySelectorAll(type === 'Transverse'? '.jt_t' : '.jt_l')
				// 创建小圆点（小方块）
				this.createCircle(this.swiperItem, this.sw)
				//不同模式下的小圆点添加相应的样式
				this.circleItem = document.querySelectorAll(type === 'Transverse'? '.circle-item_t' : '.circle-item')
				//添加mouseenter事件
				this.mouseenter(this.sc, this.jt, this.circleItem)
				//添加mouseleave事件
				this.mouseleave(this.sc, this.jt, this.circleItem, this.rbtn)
				//给上下左右键添加click事件
				this.right(this.rbtn)
				this.left(this.lbtn)
				//开启定时器
				timer =setInterval(() => {
					this.rbtn.click()
				}, 3000)
			}
			//创建小圆点
			createCircle(si, sw) {
				// 根据轮播图的长度创建小圆点
				for(let i = 0; i < si.length - 1; ++i) {
					const el = document.createElement('i')
					if(i === 0) {
						//默认第一个小圆点为选中
						el.classList.add('selected')
					}
					//不同模式下给小圆点添加对应的类名
					el.classList.add(this.type === 'offsetHeight'? 'circle-item_t' : 'circle-item')
					// 将索引记录到自定义属性上方便使用
					el.setAttribute('data-index', i)
					// 给小圆点添加click事件
					this.cirClick(el)
					//将节点添加到dom上
					document.getElementById('circle-box').appendChild(el)
				}
			}
			// 小圆点的click事件
			cirClick(node) {
				node.addEventListener('click', () => {
					// 如果当前为true点击小圆点才有效果
					if(this.debounce) {
						this.debounce = false
						// 记录当前的index值
						let initVlaue = this.index
						// 将index值改变为小圆点自定义属性上的值
						this.index = node.getAttribute('data-index') - 0
						// 如果index当前值比记录的值小 那么是往前滚动
						if(initVlaue - this.index >0) {
							this.animate(this.sw, this.distance, false, () => {
								this.debounce = true
								this.translateDistance = this.index * this.distance
							})
							// 如果index当前值比记录的值大 那么是往后滚动
						}else if(initVlaue - this.index <0){
							this.animate(this.sw, this.distance, true, () => {
								this.debounce = true
								this.translateDistance = this.index * this.distance
							})
						//相等就返回
						}else {
							return
						}
						//给点击的小圆点添加selected类， 其余的删除selected类
						this.removeCircleStyle(this.index)
					}
				})
			}
			//mouseenter事件
			mouseenter(node, jt, ci) {
				node.addEventListener('mouseenter', function() {
					[...jt].forEach(function(el) {
						//高亮上下左右箭头
						el.classList.add('enter')
					});
					[...ci].forEach(function(el) {
						//高亮小圆点
						el.classList.add('show')
					});
					//清空定定时器
					clearInterval(timer)
				})
			}
			//mouseleave事件
			mouseleave(node, jt, ci, r) {
				node.addEventListener('mouseleave', function() {
					[...jt].forEach(function(el) {
						//移除高亮
						el.classList.remove('enter')
					});
					[...ci].forEach(function(el) {
						//移除高亮
						el.classList.remove('show')
					});
					// 开启定时器
					timer = setInterval(() => {
						r.click()
					}, 3000)
				})
				
			}
			//上和左箭头的click事件
			left(node) {
				node.addEventListener('click', () => {
					if(!this.debounce) return
					this.debounce = false
					this.index --
					// 当前是第一张轮播图的时候
					if(this.index < 0) {
						this.index = this.swiperItem.length - 2
						//将轮播图盒子移动到最后一个节点
						this.sw.style.transform = this.type === 'offsetHeight' ? 'translate3d(0, -'+(this.index * this.distance)+'px, 0)' : 'translate3d(-'+(this.index * this.distance)+'px, 0, 0)'
					}
					// 把当前盒子移动的距离记录下来
					this.translateDistance = (this.index+1) * this.distance
					//修改小圆点状态
					this.removeCircleStyle(this.index)
					//开启动画
					this.animate(this.sw, this.distance, false, () => {
						//回调函数，动画完成后可再次点击箭头或小圆点
						this.debounce = true
					})
				})
				
			}
			//下和右箭头的click事件
			right(node) {
				node.addEventListener('click', () => {
					if(!this.debounce) return
					this.debounce = false
					this.translateDistance = this.index * this.distance
					if(this.index === 0) {
						this.sw.style.transform = 'translate3d(0, 0, 0)'
					}
					this.index ++
					this.animate(this.sw, this.distance, true, () => {
						this.debounce = true
						if(this.index >= this.swiperItem.length-1) {
							this.index = 0
						}
					})
					this.removeCircleStyle(this.index===this.swiperItem.length-1? 0 : this.index)
				})
			}
			//obj 为需要移动的对象， target为移动的距离， f为移动的方向， callback为回调函数
			animate(obj,target,f = true,callback){
				let h
				// 初始移动位置
				let step = 0 + (this.translateDistance -0);
				clearInterval(obj.timer)
				obj.timer=setInterval(() => {
					//实时获取盒子当前移动的距离
					//qq浏览器不支持 ？
					// h = (obj.style.transform?.split('(')[1]?.match(/\d+/g)[this.type === 'offsetHeight' ? 1 : 0] - 0)|| 0
					h = obj.style.transform&&obj.style.transform.split('(')[1]&&obj.style.transform.split('(')[1].match(/\d+/g)[this.type === 'offsetHeight' ? 1 : 0] - 0|| 0
					//缓动公式
					step += (target * this.index - h) /10 
					// f变量为true代表往后移动,false代表向前移动， 根据不同的方向进行取整
					step=f?Math.ceil(step):Math.floor(step);
					obj.style.transform = this.type === 'offsetHeight' ? 'translate3d(0, -'+step+'px, 0)' : 'translate3d(-'+step+'px, 0, 0)';
						// 当step叠加后等于盒子移动的距离
					if(step === h){
						obj.style.transform = this.type === 'offsetHeight' ? 'translate3d(0, -'+step+'px, 0)' : 'translate3d(-'+step+'px, 0, 0)';
						clearInterval(obj.timer)
						callback&&callback();
					}
				},16)
			}
			//根据this.index改变小圆点状态
			removeCircleStyle(index) {
				[...this.circleItem].forEach(function(item) {
					item.classList.remove('selected')
				});
				this.circleItem[index].classList.add('selected')
			}
			//初始化节点
			initNode() {
				const container =  document.querySelector('.swiper-container').innerHTML = `
					<div class="swiper-wraper">
						<div class="swiper-item" style="background-color: red;"></div>
						<div class="swiper-item" style="background-color: green;"></div>
						<div class="swiper-item" style="background-color: blue;"></div>
						<div class="swiper-item" style="background-color: gray;"></div>
						<div class="swiper-item" style="background-color: orange;"></div>
					</div>
					<div id="circle-box" class="circle">
					</div>
					// 根据不同的模式显示对应的箭头
					<div class="jt_l right ${type === 'Transverse'? 'btn_hide' : 'btn_show'}">→</div>
					<div class="jt_l left ${type === 'Transverse'? 'btn_hide' : 'btn_show'}">←</div>
					<div class="jt_t top ${type === 'Transverse'? 'btn_show' : 'btn_hide'}">↑</div>
					<div class="jt_t bottom ${type === 'Transverse'? 'btn_show' : 'btn_hide'}">↓</div>
				`
			}
		}
		//默认加载纵向轮播图实列
		new Animate(type)
		$('.change-type').addEventListener('click', function(e) {
			// 改变轮播图模式
			if(type === 'Transverse') {
				type = 'Longitudinal'
				new Animate(type)
				$('.change-type').innerHTML = '纵向轮播'
			}else {
				type = 'Transverse'
				new Animate(type)
				$('.change-type').innerHTML = '横向轮播'
			}
		})
	</script>
</body>

</html>